package com.chj.comp.common.core.util;

import com.chj.comp.common.core.constant.EncryptAndDecryptConstant;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;

/**
 * 基于AES的加解密工具类，加解密方法等同于mysql对应函数
 * 加密：
 * encryptAESECB()
 * select TO_BASE64(AES_ENCRYPT('123456', '0123456789ABC123'));
 * 解密：
 * decryptAESECB()
 * select AES_DECRYPT(FROM_BASE64('AeHspI10/zHWoWTlAg1h/g=='),'0123456789ABC123');
 *
 * @author cuihui
 * @date 2022/03/11
 */
public class AESUtil {
    private static final Charset UTF_8 = StandardCharsets.UTF_8;
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/NOPadding";
    private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";
    private static final String DEFAULT_ENCODING = "UTF-8";

    /**
     * 加密
     *
     * @param data 需要加密的内容
     * @param key  加密密码
     * @return
     */
    public static byte[] encrypt(byte[] data, byte[] key) {
        CheckUtil.notEmpty(data, "data");
        CheckUtil.notEmpty(key, "key");
        if (key.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
        }
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");
            /**
             * 创建密码器
             */
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);  //NOSONAR
            cipher.init(Cipher.ENCRYPT_MODE, seckey);// 初始化
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    /**
     * 解密
     *
     * @param data 待解密内容
     * @param key  解密密钥
     * @return
     */
    public static byte[] decrypt(byte[] data, byte[] key) {
        CheckUtil.notEmpty(data, "data");
        CheckUtil.notEmpty(key, "key");
        if (key.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
        }
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");
            // 创建密码器
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);  //NOSONAR
            cipher.init(Cipher.DECRYPT_MODE, seckey);// 初始化
            return cipher.doFinal(data);
        } catch (Exception e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    public static String encryptToBase64(String data, String key) {
        try {
            byte[] valueByte = encrypt(data.getBytes(DEFAULT_ENCODING), key.getBytes(DEFAULT_ENCODING));
            String temp = Base64.getUrlEncoder().encodeToString(valueByte);
            if (temp.indexOf("=") >= 0) {
                temp = temp.replaceAll("=", "");
            }
            return temp;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("encrypt fail!", e);
        }

    }

    /**
     * AES解密 ECB 加密
     * @param src 加密之后的数据
     * @return 解密之后的数据
     */
    public static String decryptAESECB(String src, String key) {
        String decryptResult = "";
        byte[] aesBytes =  decrypt(Base64.getDecoder().decode(src), key.getBytes());
        decryptResult = new String(aesBytes, StandardCharsets.UTF_8);
        return decryptResult;
    }

    /**
     * AES 加密 ECB 加密
     * @param src 需要加密的
     * @return 加密之后 的结果
     */
    public static String encryptAESECB(String src, String key) {
        String encryptResult = "";
        byte[] aesBytes =  encrypt(src.getBytes(), key.getBytes());
        encryptResult = Base64.getEncoder().encodeToString(aesBytes);
        return encryptResult;
    }

    public static String decryptFromBase64(String data, String key) throws UnsupportedEncodingException{
        try {
            byte[] originalData = Base64.getUrlDecoder().decode(data.getBytes());
            byte[] valueByte = decrypt(originalData, key.getBytes(EncryptAndDecryptConstant.CHAR_ENCODING));
            return new String(valueByte, EncryptAndDecryptConstant.CHAR_ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    public static String encryptWithKeyBase64(String data, String key) {
        try {
            byte[] valueByte = encrypt(data.getBytes(DEFAULT_ENCODING), Base64.getDecoder().decode(key.getBytes()));
            return new String(Base64.getUrlEncoder().encode(valueByte));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    public static byte[] genarateRandomKey() {
        KeyGenerator keygen = null;
        try {
            keygen = KeyGenerator.getInstance(AES_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(" genarateRandomKey fail!", e);
        }
        SecureRandom random = new SecureRandom();
        keygen.init(random);
        Key key = keygen.generateKey();
        return key.getEncoded();
    }



    public static String decryptAES(String data, String pass) throws Exception {
        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
        SecretKeySpec keyspec = new SecretKeySpec(pass.getBytes(), KEY_ALGORITHM);
        IvParameterSpec ivspec = new IvParameterSpec(pass.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
        byte[] result = cipher.doFinal(Base64.getUrlDecoder().decode(data.getBytes(DEFAULT_ENCODING)));
        return new String(result, DEFAULT_ENCODING);
    }

    /**
     * 微信小程序AES解密.
     *
     * @param sessionKey    session_key
     * @param encryptedData 消息密文
     * @param ivStr         iv字符串
     */
    public static String decryptWxmp(String sessionKey, String encryptedData, String ivStr) {
        try {
            AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
            params.init(new IvParameterSpec(org.apache.commons.codec.binary.Base64.decodeBase64(ivStr)));

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(sessionKey), "AES"), params);

            return new String(PKCS7Util.decode(cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(encryptedData))), UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("decryptWxmp fail!", e);
        }
    }

    /**
     * AES解密.
     *
     * @param sessionKey    session_key
     * @param encryptedData 消息密文
     * @param ivStr         iv字符串
     */
    public static String decryptAnotherWay(String sessionKey, String encryptedData, String ivStr) {
        byte[] keyBytes = org.apache.commons.codec.binary.Base64.decodeBase64(sessionKey.getBytes(UTF_8));

        int base = 16;
        if (keyBytes.length % base != 0) {
            int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0);
            byte[] temp = new byte[groups * base];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
            keyBytes = temp;
        }

        Security.addProvider(new BouncyCastleProvider());
        Key key = new SecretKeySpec(keyBytes, "AES");
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(org.apache.commons.codec.binary.Base64.decodeBase64(ivStr.getBytes(UTF_8))));
            return new String(cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(encryptedData.getBytes(UTF_8))), UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("decryptAnotherWay fail!", e);
        }
    }
}
